/*
 *    Copyright (C) 2007-2009 Aike J Sommer
 *
 *
 *    This program is free software: you can
 *    redistribute it and/or modify it under the terms of the GNU
 *    General Public License as published by the Free Software
 *    Foundation, either version 3 of the License, or (at your
 *    option) any later version.
 *
 *    This program is distributed in the hope
 *    that it will be useful, but WITHOUT ANY WARRANTY; without even
 *    the implied warranty of MERCHANTABILITY or FITNESS FOR A
 *    PARTICULAR PURPOSE.  See the GNU General Public License for
 *    more details.
 *
 *    You should have received a copy of the GNU General Public License
 *    along with this program.  If not, see
 *    <http://www.gnu.org/licenses/>.
 *
 *
 *    You can reach the author and get more information about this
 *    project at: http://aikesommer.name/ or
 *    http://code.google.com/p/viewncontrol
 */

package name.aikesommer.viewncontrol.web;

import de.divis_hagen.json.JSONContext;
import de.divis_hagen.json.JSONException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Map;
import java.util.NoSuchElementException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import name.aikesommer.viewncontrol.protocol.SliceStore;
import name.aikesommer.viewncontrol.protocol.VNCException;
import name.aikesommer.viewncontrol.web.control.ScreenViewController;
import name.aikesommer.viewncontrol.web.xml.CursorUpdate;
import name.aikesommer.viewncontrol.web.xml.ScreenSlice;
import name.aikesommer.viewncontrol.web.xml.ScreenSlices;

/**
 *
 * @author Aike J Sommer
 */
public class ScreenViewServlet extends HttpServlet {

    public static enum ResultType {

        XML, JSON
    }

    public static enum Mode {

        CheckForSlices, FetchSlice, CheckForCursorUpdates
    }

    public ScreenViewServlet() {
    }

    /** 
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        PrintWriter out = null;
        try {
            ScreenViewController controller = ScreenViewController.self(request.getSession());

            String modeParam = request.getParameter("mode");
            Mode mode = Mode.valueOf(
                    modeParam);
            switch (mode) {
                case CheckForSlices:
                     {
                        long since = Long.parseLong(
                                request.getParameter("since"));
                        boolean fullUpdate = Boolean.parseBoolean(request.
                                getParameter("full-update"));

                        out = response.getWriter();
                        Map<Long, SliceStore.Slice> slices;
                        try {
                            if (fullUpdate) {
                                controller.purgeAll();
                            }
                            slices = controller.getSlices(since, 30000);
                            if (!fullUpdate && slices.size() > 50) {
                                controller.purgeAll();
                                slices = controller.getSlices(since, 10000);
                            }
                        } catch (VNCException.ConnectionLost ex) {
//                            Log.debugS("lost vnc connection");
//                            Log.debugS(ex);
                            response.setContentType("text/plain;charset=UTF-8");
                            out.println("connection lost");
                            return;
                        }

                        ScreenSlices result = new ScreenSlices();
                        int i = 0;
                        for (Map.Entry<Long, SliceStore.Slice> slice : slices.
                                entrySet()) {
                            String debugInfo = createDebugInfo(
                                    slice.getValue().info);
                            result.getSlices().add(new ScreenSlice(
                                    slice.getKey(), slice.getValue().x, slice.
                                    getValue().y, slice.getValue().width, slice.
                                    getValue().height, debugInfo));
                            if (++i >= 10) {
                                break;
                            }
                        }

                        String resultTypeParam = request.getParameter(
                                "result-type");
                        ResultType resultType = resultTypeParam == null ? ResultType.XML
                                : ResultType.valueOf(resultTypeParam);
                        switch (resultType) {
                            case XML:
                                response.setContentType("text/xml;charset=UTF-8");
                                try {
                                    JAXBContext context = JAXBContext.
                                            newInstance(ScreenSlices.class);
                                    Marshaller marshaller = context.
                                            createMarshaller();
                                    marshaller.setProperty(
                                            Marshaller.JAXB_FORMATTED_OUTPUT,
                                            true);
                                    marshaller.marshal(result, out);
                                } catch (JAXBException ex) {
                                    throw new RuntimeException(ex);
                                }
                                break;
                            case JSON:
                                response.setContentType(
                                        "text/json;charset=UTF-8");
                                try {
                                    JSONContext context = JSONContext.
                                            newInstance(ScreenSlices.class);
                                    de.divis_hagen.json.Marshaller marshaller =
                                            context.createMarshaller();
                                    marshaller.marshall(result, out);
                                } catch (JSONException ex) {
                                    throw new RuntimeException(ex);
                                }
                                break;
                            default:
                                throw new IllegalArgumentException("result type: " +
                                        resultTypeParam);
                        }
                    }
                    break;
                case CheckForCursorUpdates:
                     {
                        long since = Long.parseLong(
                                request.getParameter("since"));

                        out = response.getWriter();
                        SliceStore.Slice slice = null;
                        try {
                            slice = controller.getCursorUpdate(since, 30000);
                        } catch (VNCException.ConnectionLost ex) {
//                            Log.debugS("lost vnc connection");
//                            Log.debugS(ex);
                            response.setContentType("text/plain;charset=UTF-8");
                            out.println("connection lost");
                            return;
                        }

                        CursorUpdate result;
                        if (slice == null) {
                            result = new CursorUpdate();
                        } else {
                            String debugInfo = createDebugInfo(slice.info);
                            result = new CursorUpdate(new ScreenSlice(
                                    (Long) slice.info.get("slice-id"), slice.x,
                                    slice.y, slice.width, slice.height,
                                    debugInfo), slice.hasData());
                        }

                        String resultTypeParam = request.getParameter(
                                "result-type");
                        ResultType resultType = resultTypeParam == null ? ResultType.XML
                                : ResultType.valueOf(resultTypeParam);
                        switch (resultType) {
                            case XML:
                                response.setContentType("text/xml;charset=UTF-8");
                                try {
                                    JAXBContext context = JAXBContext.
                                            newInstance(CursorUpdate.class);
                                    Marshaller marshaller = context.
                                            createMarshaller();
                                    marshaller.setProperty(
                                            Marshaller.JAXB_FORMATTED_OUTPUT,
                                            true);
                                    marshaller.marshal(result, out);
                                } catch (JAXBException ex) {
                                    throw new RuntimeException(ex);
                                }
                                break;
                            case JSON:
                                response.setContentType(
                                        "text/json;charset=UTF-8");
                                try {
                                    JSONContext context = JSONContext.
                                            newInstance(CursorUpdate.class);
                                    de.divis_hagen.json.Marshaller marshaller =
                                            context.createMarshaller();
                                    marshaller.marshall(result, out);
                                } catch (JSONException ex) {
                                    throw new RuntimeException(ex);
                                }
                                break;
                            default:
                                throw new IllegalArgumentException("result type: " +
                                        resultTypeParam);
                        }
                    }
                    break;
                case FetchSlice:
                     {
                        long id = Long.parseLong(request.getParameter("id"));
                        try {
                            SliceStore.Slice slice = controller.fetchSlice(id);
                            response.setContentType(slice.type().mimetype());
                            response.getOutputStream().write(slice.data());
                        } catch (NoSuchElementException ex) {
                            response.sendError(response.SC_NOT_FOUND);
                        }
                        response.getOutputStream().close();
                        controller.purgeSlice(id);
                    }
                    break;
                default:
                    throw new IllegalArgumentException("mode: " + modeParam);
            }
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    private String createDebugInfo(Map<String, Object> info) {
        StringBuffer result = null;
        for (Map.Entry<String, Object> entry : info.entrySet()) {
            String value =
                    entry.getKey() + ": ''" + entry.getValue().toString() + "''";
            if (result == null) {
                result = new StringBuffer(value);
            } else {
                result.append("; ");
                result.append(value);
            }
        }
        return result == null ? "" : result.toString();
    }

    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /** 
     * Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {
        processRequest(request, response);
    }

    /** 
     * Returns a short description of the servlet.
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>
}
